//                 _              
//                | |             
//   __ _ _ __ ___| |__ __  _____ 
//  / _` | '__/ __| '_ \\ \/ / __|
// | (_| | | | (__| | | |>  <\__ \
//  \__,_|_|  \___|_| |_/_/\_\___/
//                                
// Copyright 2010 Scott Dixon http://www.archxs.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.archxs.commons.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.junit.Assert;
import org.junit.Test;

import com.archxs.commons.Variant;

/**
 * Common unit tests for implementations of the {@link VariantCollection} interface.
 */
public abstract class VariantCollectionTestCommon {

    @Test(expected=IllegalArgumentException.class)
    public void testCreateNullArgs() {
        createTestSubject(null);
    }
    
    @Test(expected=NullPointerException.class)
    public void testToArrayNullArg() {
        final VariantCollection testSubject = createTestSubject(new ArrayList<String[]>());
        testSubject.toArray(null);
    }
    
    @Test
    public void testToArray() {
        final int maxX = 100;
        final int maxY = 100;
        final Collection<String[]> test = createTestData(maxX,maxY);
        final VariantCollection testSubject = createTestSubject(test);
        final Integer[] ints = testSubject.toArray(new Integer[]{});
        Assert.assertNotNull(ints);
        for(int x = 0; x < maxX; ++x) {
            Assert.assertEquals(getValueForTestIndex(x,0), ints[x].intValue());
        }
        final Integer[][] arrayOfInts = testSubject.toArray(new Integer[][]{});
        Assert.assertNotNull(arrayOfInts);
        for(int x = 0; x < maxX; ++x) {
            for(int y = 0; y < maxY; ++y) {
                Assert.assertEquals(getValueForTestIndex(x,y), arrayOfInts[x][y].intValue());
            }
        }
    }
    
    @Test
    public void testRemove() {
        final Collection<String[]> test = createTestData(1,1);
        final VariantCollection testSubject = createTestSubject(test);
        Assert.assertEquals(1,testSubject.size());
        final String[] testValue1 = test.iterator().next();
        Assert.assertTrue(testSubject.remove(Variant.valueOf(testValue1)));
        Assert.assertEquals(0, testSubject.size());
    }
    
    @Test
    public void testAdd() {
        final Collection<String[]> test = createTestData(1,1);
        final VariantCollection testSubject = createTestSubject(test);
        Assert.assertEquals(1,testSubject.size());
        final Variant fooVar = Variant.valueOf("foo");
        Assert.assertTrue(testSubject.add(fooVar));
        Assert.assertEquals(2,testSubject.size());
        Assert.assertTrue(testSubject.remove(fooVar));
    }
    
    @Test
    public void testClear() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        Assert.assertEquals(10,testSubject.size());
        testSubject.clear();
        Assert.assertEquals(0,testSubject.size());
    }
    
    @Test
    public void testIterator() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        int nextCount = 0;
        final Iterator<Variant> i = testSubject.iterator();
        final Iterator<String[]> testI = test.iterator();
        while(i.hasNext()) {
            Assert.assertEquals(Variant.valueOf(testI.next()), i.next());
            ++nextCount;
        }
        Assert.assertEquals(10, nextCount);
    }
    
    @Test
    public void testIterator_remove() {
        final Collection<String[]> test = createTestData(3,1);
        final VariantCollection testSubject = createTestSubject(test);
        int nextCount = 0;
        final Iterator<Variant> i = testSubject.iterator();
        while(i.hasNext()) {
            i.next();
            if (nextCount == 1) {
                i.remove();
            }
            ++nextCount;
        }
        Assert.assertEquals(3, nextCount);
        Assert.assertEquals(2, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(2, test.size());
        }
    }
    
    @Test
    public void testContainsAll() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        final Collection<Variant> containsTest = new ArrayList<Variant>();
        for(final String[] testValue : test) {
            containsTest.add(Variant.valueOf(testValue));
        }
        Assert.assertTrue(testSubject.containsAll(containsTest));
        containsTest.add(Variant.valueOf("foo"));
        Assert.assertFalse(testSubject.containsAll(containsTest));
    }
    
    @Test
    public void testAddAll() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        final int firstSize = test.size();
        Assert.assertEquals(firstSize, testSubject.size());
        final Collection<Variant> allAllTest = new ArrayList<Variant>();
        allAllTest.add(Variant.valueOf("foo"));
        allAllTest.add(Variant.valueOf("fum"));
        Assert.assertTrue(testSubject.addAll(allAllTest));
        Assert.assertEquals(firstSize + 2, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(test.size(),testSubject.size());
        }
        Assert.assertFalse(testSubject.addAll(new ArrayList<Variant>()));
    }
    
    @Test
    public void testAddAllNull() {
        try {
            final Collection<String[]> test = createTestData(10,10);
            final VariantCollection testSubject = createTestSubject(test);
            testSubject.addAll(null);
        } catch (NullPointerException e) {
            // WE ARE ALLOWED TO THROW AN NPE
        }
        // OR NOT. ALL OTHER EXCEPTIONS WILL FAIL THE TEST.
    }
    
    @Test
    public void testRemoveAll() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        final int firstSize = test.size();
        Assert.assertEquals(firstSize, testSubject.size());
        final Collection<Variant> removeTest = new ArrayList<Variant>();
        for(final String[] v : test) {
            removeTest.add(Variant.valueOf(v));
        }
        Assert.assertTrue(testSubject.removeAll(removeTest));
        Assert.assertEquals(0, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(0,testSubject.size());
        }
        final Collection<Variant> noChangeTest = new ArrayList<Variant>(test.size());
        noChangeTest.add(Variant.valueOf("foo"));
        Assert.assertFalse(testSubject.removeAll(noChangeTest));
    }
    
    @Test
    public void testRemoveAllNull() {
        try {
            final Collection<String[]> test = createTestData(10,10);
            final VariantCollection testSubject = createTestSubject(test);
            testSubject.removeAll(null);
        } catch (NullPointerException e) {
            // WE ARE ALLOWED TO THROW AN NPE
        }
        // OR NOT. ALL OTHER EXCEPTIONS WILL FAIL THE TEST.
    }
    
    @Test
    public void testRetailAll() {
        final Collection<String[]> test = createTestData(10,10);
        final VariantCollection testSubject = createTestSubject(test);
        final int firstSize = test.size();
        Assert.assertEquals(firstSize, testSubject.size());
        final Collection<Variant> retainAllTest = new ArrayList<Variant>();
        int i = 0;
        for(final String[] v : test) {
            if (i++ % 2 == 0) {
                retainAllTest.add(Variant.valueOf(v));
            }
        }
        Assert.assertTrue(testSubject.retainAll(retainAllTest));
        Assert.assertEquals(firstSize / 2, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(firstSize / 2, test.size());
        }
        Assert.assertFalse(testSubject.retainAll(retainAllTest));
        Assert.assertTrue(testSubject.retainAll(new ArrayList<Variant>()));
        Assert.assertEquals(0, testSubject.size());
        if (isLive()) {
            Assert.assertEquals(0, test.size());
        }
    }
    
    @Test
    public void testRetainAllNull() {
        try {
            final Collection<String[]> test = createTestData(10,10);
            final VariantCollection testSubject = createTestSubject(test);
            testSubject.retainAll(null);
        } catch (NullPointerException e) {
            // WE ARE ALLOWED TO THROW AN NPE
        }
        // OR NOT. ALL OTHER EXCEPTIONS WILL FAIL THE TEST.
    }
    // +-----------------------------------------------------------------------+
    // | TEMPLATE METHODS
    // +-----------------------------------------------------------------------+
    /**
     * Requests that the implementing class create a new VariantCollection instance
     * populated with the provided values.
     * @param  testValues   Values to populate the test subject with.
     * @return A new VariantCollection instance populated with the test values.
     */
    protected abstract VariantCollection createTestSubject(Collection<String[]> testValues);
    
    /**
     * Queries the implementing class about the "liveness" of its collection objects
     * returned from the {@link VariantCollectionTestCommon#createTestSubject(Collection)}
     * method. 
     * @return <code>true</code> if the VariantCollection classes created for testing
     *         are using the test values provided "live" (i.e. changes to the VariantCollection
     *         collection will cause changes to the test values). <code>false</code>
     *         indicates that all test values are, at-least, shallow copied into
     *         the test collection created.
     */
    protected abstract boolean isLive();

    // +-----------------------------------------------------------------------+
    // | PRIVATE
    // +-----------------------------------------------------------------------+
    final int getValueForTestIndex(final int x, final int y) {
        return ((x+1)*(y+1));
    }
        
    final Collection<String[]> createTestData(final int datasetSize, final int dataitemSize) {
        final Collection<String[]> test = new ArrayList<String[]>(datasetSize + 1);
        for(int x = 0; x < datasetSize; ++x) {
            final String[] dataItem = new String[dataitemSize + 1];
            for(int y = 0; y < dataItem.length; ++y) {
                dataItem[y] = Integer.toString(getValueForTestIndex(x,y));
            }
            test.add(dataItem);
        }
        return test;
    }
}
